#include "Scene_PCH.h"
#include "Camera.h"

/*--------------------------------------------------------------------------*/

Camera::Camera(const glm::vec4& viewport, float farDist, float nearDist, float fovDeg) :
	m_viewport(viewport),
	m_farDistance(farDist),
	m_nearDistance(nearDist),
	m_fovDeg(fovDeg),
	m_movementSpeed(1.0f),
	m_rotationSpeed(1.0f),
	m_dirtyBits(ECameraChange::All)
{
	Update();
}

/*--------------------------------------------------------------------------*/

Camera::~Camera()
{

}

/*--------------------------------------------------------------------------*/

const GLM_MAT4x4& Camera::GetViewMatrix()
{
	Update();
	return m_viewMatrix;
}

/*--------------------------------------------------------------------------*/

const GLM_MAT4x4& Camera::GetProjectionMatrix()
{
	Update();
	return m_projectionMatrix;
}

/*--------------------------------------------------------------------------*/

const glm::vec3& Camera::GetWorldPosition() const
{
	return m_worldPosition;
}

/*--------------------------------------------------------------------------*/

void Camera::SetWorldPosition( const glm::vec3& pos )
{
	m_dirtyBits |= ECameraChange::WorldPosition;
	m_worldPosition = pos;
}

/*--------------------------------------------------------------------------*/

const glm::quat& Camera::GetRotation() const
{
	return m_rotation;
}

/*--------------------------------------------------------------------------*/

void Camera::SetRotation( const glm::quat& quat )
{
	m_dirtyBits |= ECameraChange::Rotation;
	m_rotation = quat;
}

/*--------------------------------------------------------------------------*/

float Camera::GetFoV() const
{
	return m_fovDeg;
}

/*--------------------------------------------------------------------------*/

void Camera::SetFov( float fov )
{
	m_dirtyBits |= ECameraChange::FieldOfView;
	m_fovDeg = fov;
}

/*--------------------------------------------------------------------------*/

void Camera::UpdateViewMatrix()
{
#ifdef USE_GLM_SIMD
	m_viewMatrix = glm::detail::fmat4x4SIMD(glm::toMat4(m_rotation) * glm::translate(-m_worldPosition));
#else
	m_viewMatrix = glm::toMat4(m_rotation) * glm::translate(-m_worldPosition);
#endif
}

/*--------------------------------------------------------------------------*/

void Camera::UpdateProjectionMatrix()
{
	float aspect = (m_viewport.z - m_viewport.x) / (m_viewport.w - m_viewport.y);

#ifdef USE_GLM_SIMD
	m_projectionMatrix = glm::detail::fmat4x4SIMD(	glm::perspective(	m_fovDeg,
																		aspect,
																		m_nearDistance,
																		m_farDistance) );
#else
	m_projectionMatrix = glm::perspective(	m_fovDeg,
											aspect,
											m_nearDistance,
											m_farDistance);
#endif
}

/*--------------------------------------------------------------------------*/

void Camera::UpdateFrustum()
{
	GLM_MAT4x4 vp_mat = m_projectionMatrix * m_viewMatrix;

#ifdef USE_GLM_SIMD
	const float* vp = (float*) &vp_mat.Data[0].x;
#else
	const float* vp = glm::value_ptr(vp_mat);
#endif

	m_planeLeft		= glm::vec4(vp[3] + vp[0], vp[7] + vp[4], vp[11] + vp[8], vp[15] + vp[12]);
	m_planeRight	= glm::vec4(vp[3] - vp[0], vp[7] - vp[4], vp[11] - vp[8], vp[15] - vp[12]);
	m_planeBottom	= glm::vec4(vp[3] + vp[1], vp[7] + vp[5], vp[11] + vp[9], vp[15] + vp[13]);
	m_planeTop		= glm::vec4(vp[3] - vp[1], vp[7] - vp[5], vp[11] - vp[9], vp[15] - vp[13]);
	m_planeNear		= glm::vec4(vp[3] + vp[2], vp[7] + vp[6], vp[11] + vp[10], vp[15] + vp[14]);
	m_planeFar		= glm::vec4(vp[3] - vp[2], vp[7] - vp[6], vp[11] - vp[10], vp[15] - vp[14]);

	m_planeLeft		/= glm::fastLength(glm::vec3(m_planeLeft.x, m_planeLeft.y, m_planeLeft.z));
	m_planeRight	/= glm::fastLength(glm::vec3(m_planeRight.x, m_planeRight.y, m_planeRight.z));
	m_planeBottom	/= glm::fastLength(glm::vec3(m_planeBottom.x, m_planeBottom.y, m_planeBottom.z));
	m_planeTop		/= glm::fastLength(glm::vec3(m_planeTop.x, m_planeTop.y, m_planeTop.z));
	m_planeNear		/= glm::fastLength(glm::vec3(m_planeNear.x, m_planeNear.y, m_planeNear.z));
	m_planeFar		/= glm::fastLength(glm::vec3(m_planeFar.x, m_planeFar.y, m_planeFar.z));
}

/*--------------------------------------------------------------------------*/

void Camera::Update()
{
	if (m_dirtyBits)
	{
		if (m_dirtyBits & (ECameraChange::WorldPosition | ECameraChange::Rotation))
		{
			UpdateViewMatrix();
		}

		if (m_dirtyBits & ECameraChange::FieldOfView)
		{
			UpdateProjectionMatrix();
		}

		if (m_dirtyBits & (ECameraChange::WorldPosition | ECameraChange::Rotation | ECameraChange::FieldOfView))
		{
			UpdateFrustum();
		}

		m_dirtyBits = 0;
	}
}

/*--------------------------------------------------------------------------*/

void Camera::AddWorldPosition( const glm::vec3& diff )
{
	m_worldPosition += diff;
	m_dirtyBits |= ECameraChange::WorldPosition;
}

/*--------------------------------------------------------------------------*/

void Camera::AddRotationX( float deg )
{
	Update();	
#ifdef USE_GLM_SIMD
	m_rotation = m_rotation * glm::angleAxis(deg * m_rotationSpeed * g_Kernel->GetDeltaTime(), glm::vec3(m_viewMatrix.Data[0].x, m_viewMatrix.Data[1].x, m_viewMatrix.Data[2].x));
#else
	m_rotation = m_rotation * glm::angleAxis(deg * m_rotationSpeed * g_Kernel->GetDeltaTime(), glm::vec3(m_viewMatrix[0][0], m_viewMatrix[1][0], m_viewMatrix[2][0]));
#endif
	m_dirtyBits |= ECameraChange::Rotation;
}

/*--------------------------------------------------------------------------*/

void Camera::AddRotationY( float deg )
{
	Update();	
#ifdef USE_GLM_SIMD
	m_rotation = m_rotation * glm::angleAxis(deg * m_rotationSpeed * g_Kernel->GetDeltaTime(), glm::vec3(m_viewMatrix.Data[0].y, m_viewMatrix.Data[1].y, m_viewMatrix.Data[2].y));
#else
	m_rotation = m_rotation * glm::angleAxis(deg * m_rotationSpeed * g_Kernel->GetDeltaTime(), glm::vec3(m_viewMatrix[0][1], m_viewMatrix[1][1], m_viewMatrix[2][1]));
#endif
	m_dirtyBits |= ECameraChange::Rotation;
}

/*--------------------------------------------------------------------------*/

void Camera::AddRotationZ( float deg )
{
	Update();
#ifdef USE_GLM_SIMD
	m_rotation = m_rotation * glm::angleAxis(deg * m_rotationSpeed * g_Kernel->GetDeltaTime(), glm::vec3(m_viewMatrix.Data[0].z, m_viewMatrix.Data[1].z, m_viewMatrix.Data[2].z));
#else
	m_rotation = m_rotation * glm::angleAxis(deg * m_rotationSpeed * g_Kernel->GetDeltaTime(), glm::vec3(m_viewMatrix[0][2], m_viewMatrix[1][2], m_viewMatrix[2][2]));
#endif
	m_dirtyBits |= ECameraChange::Rotation;
}

/*--------------------------------------------------------------------------*/

void Camera::AddWorldPositionX( float diff )
{
	m_worldPosition.x += diff;
	m_dirtyBits |= ECameraChange::WorldPosition;
}

/*--------------------------------------------------------------------------*/

void Camera::AddWorldPositionY( float diff )
{
	m_worldPosition.y += diff;
	m_dirtyBits |= ECameraChange::WorldPosition;
}

/*--------------------------------------------------------------------------*/

void Camera::AddWorldPositionZ( float diff )
{
	m_worldPosition.z += diff;
	m_dirtyBits |= ECameraChange::WorldPosition;
}

/*--------------------------------------------------------------------------*/

void Camera::MoveX( float diff )
{
	Update();
#ifdef USE_GLM_SIMD
	m_worldPosition += glm::normalize(glm::vec3(m_viewMatrix.Data[0].x, m_viewMatrix.Data[1].x, m_viewMatrix.Data[2].x)) * diff * m_rotationSpeed * g_Kernel->GetDeltaTime();
#else
	m_worldPosition += glm::normalize(glm::vec3(m_viewMatrix[0][0], m_viewMatrix[1][0], m_viewMatrix[2][0])) * diff * m_rotationSpeed * g_Kernel->GetDeltaTime();
#endif
	m_dirtyBits |= ECameraChange::WorldPosition;
}

/*--------------------------------------------------------------------------*/

void Camera::MoveY( float diff )
{
	Update();
#ifdef USE_GLM_SIMD
	m_worldPosition += glm::normalize(glm::vec3(m_viewMatrix.Data[0].y, m_viewMatrix.Data[1].y, m_viewMatrix.Data[2].y)) * diff * m_rotationSpeed * g_Kernel->GetDeltaTime();
#else
	m_worldPosition += glm::normalize(glm::vec3(m_viewMatrix[0][1], m_viewMatrix[1][1], m_viewMatrix[2][1])) * diff * m_rotationSpeed * g_Kernel->GetDeltaTime();
#endif
	m_dirtyBits |= ECameraChange::WorldPosition;
}

/*--------------------------------------------------------------------------*/

void Camera::MoveZ( float diff )
{
	Update();
#ifdef USE_GLM_SIMD
	m_worldPosition += glm::normalize(glm::vec3(m_viewMatrix.Data[0].z, m_viewMatrix.Data[1].z, m_viewMatrix.Data[2].z)) * diff * m_rotationSpeed * g_Kernel->GetDeltaTime();
#else
	m_worldPosition += glm::normalize(glm::vec3(m_viewMatrix[0][2], m_viewMatrix[1][2], m_viewMatrix[2][2])) * diff * m_rotationSpeed * g_Kernel->GetDeltaTime();
#endif
	m_dirtyBits |= ECameraChange::WorldPosition;
}

/*--------------------------------------------------------------------------*/

float Camera::GetFarDistance() const
{
	return m_farDistance;
}

/*--------------------------------------------------------------------------*/

float Camera::GetNearDistance() const
{
	return m_nearDistance;
}

/*--------------------------------------------------------------------------*/